9.
2項演算
1つのUGenの後にオペレータとオペランドが続く2項演算は、通常は、単項演算によって生成されるよりもより複雑なシンセシス・プロセスを作り出します。
2項演算の例としては、ほんのいくつか例を挙げると、加算、乗算、減算、割算といったものがあります。
一般的なパターンは
OBJECT OPERATOR OPERAND
オブジェクト オペレータ オペランド
であり、例えば次の様に
2 * 3
オブジェクト、2項演算子、オペランド、となります。
UGenに対して適用することのできる2項演算の多くのリストを見るには、SuperColliderのヘルプ・システムでHelp/BinaryOpsを参照して下さい。
////////////////////////////////////////////////////////////////////////////////////////////////////
ミキシング=加算
2つ、またはそれ以上のUGenをミックスするには加算します。
(
// 2つのノコギリ波をミックスする
{
Saw.ar(500, 0.05) // オブジェクト
+ // オペレータ
Saw.ar(600, 0.06) // オペランド
}.scope;
)
加算の結果は1つのBinaryOpUGenになります。
////////////////////////////////////////////////////////////////////////////////////////////////////
次の例は、3つのUGenが加算を通して合体することによって何が起きるのかを示します。最初に、2つのUGenが1つのBinarOpUGenに合体します。その結果に加算演算子ともう1つのUGen(オペランド)が続き、もう1つのBinaryOpUGenを生成します。
(
{
// 最初の2つのUGenは1つのBinaryOpUGenに合体する
Saw.ar(500, 0.05)
+
Saw.ar(600, 0.06)
+
// このUGenはBinaryOpUGenに対するオペランドと‘+’オペレータ
Saw.ar(700, 0.07)
}.scope;
)
////////////////////////////////////////////////////////////////////////////////////////////////////
MixというUGenを用いることで前の例と同じ結果になります。
(
{
// ミックスしたいUGenを配列の中に入れる
Mix.ar(
[
Saw.ar(500, 0.05),
Saw.ar(600, 0.06),
Saw.ar(700, 0.06)
]
)
}.scope
)
////////////////////////////////////////////////////////////////////////////////////////////////////
もしくは、Mix.arFillを使って似た様な結果を生成することもできます。この関数が実行される時には毎回、アーギュメントiはインクリメントされます。つまり、1回目に関数が実行される時にはiは0に等しく、2回目には1、3回目には2、という様になります。
{ Mix.arFill(3, { arg i; Saw.ar(500 + ((i+1) * 100), 0.05) }) }.scope;
////////////////////////////////////////////////////////////////////////////////////////////////////
スケーリング=掛算
低い周波数のオシレータを用いる次の例は、WhiteNoiseジェネレータに対してエンベロープを適用します。これは、1つのUGenを他のUGenと掛算することにより、双方をスケーリングするということをデモするものです。
{ WhiteNoise.ar(0.1) * SinOsc.kr(1, 1) }.scope;
次の例は、2つのノイズを生成するUgenがサイン波でスケーリングされてBinaryOpUGenを生成し、それがまた別のBinaryOpUGenに加算されるというものです。
(
// ... 汽車のまね?
{
(WhiteNoise.ar(0.1) * SinOsc.kr(1, 1))
+
(BrownNoise.ar(0.1) * SinOsc.kr(2, 1))
}.scope;
)
////////////////////////////////////////////////////////////////////////////////////////////////////
エンベロープ
UGenの任意のアスペクト、例えば、周波数、位相、振幅といったものをダイナミックに変調するにはエンベロープを使います。
// 振幅を変調する
{ SinOsc.ar(440, 0, 0.1) * EnvGen.kr(Env.sine(1), doneAction: 2) }.scope;
doneActionアーギュメント(コントロール)を2にセットすると、SuperColliderはSinOscとEnvGenのインスタンスを保持するために要求されていたメモリを解放することを確約します。アーギュメントがキーワードのスタイルで与えられていることに注意して下さい。キーワードのスタイルとは、アーギュメントの名前の後にコロン(‘:’)が続き、コロンの後に値が続く、というものです。
キーワードはコードをより読みやすいものにします。また、これによってアーギュメントを任意の順番で書くことができる様になります。
////////////////////////////////////////////////////////////////////////////////////////////////////
EnvGenのtimeScaleアーギュメント(コントロール)は、エンベロープの継続時間を変調します。
(
SynthDef("timeScale", { arg ts = 1;
Out.ar(
0,
SinOsc.ar(440, 0, 0.4) * EnvGen.kr(Env.sine(1), doneAction: 2, timeScale: ts)
)
}).load(s);
)
Synth("timeScale", [\ts, 0.1]); // timeScaleコントロールはエンベロープの継続時間
////////////////////////////////////////////////////////////////////////////////////////////////////
// ... エンベロープの時間をシンセが作られるたびに毎回違ったものにスケーリングする
(
r = Routine({
loop({
Synth("timeScale", [\ts, 0.01.rrand(0.3)]);
0.5.wait;
})
});
)
r.play
////////////////////////////////////////////////////////////////////////////////////////////////////
加算合成
加算合成とはその名が示す通りのものです。コンポーネントはお互いに加えられ(または合計され)ます。
(
{ // 関数を12回実行する
var n = 12;
Mix.arFill(
n,
{
SinOsc.ar(
[67.0.rrand(2000), 67.0.rrand(2000)],
0,
n.reciprocal * 0.75
)
}
)
*
EnvGen.kr(Env.perc(11, 6), doneAction: 2)
}.scope
)
////////////////////////////////////////////////////////////////////////////////////////////////////
それぞれのコンポーネントにエンベロープを与える
加算合成に期待されて来たこととは、求められる周波数成分に従ってサイン波を加算することで、想像しうるサウンドを生成または再現することができるということでした。
加算合成の問題点とは、サウンドの正確な周波数成分を知る必要があるということで、それには必要とされる数のサイン波のそれぞれが理想的なエンベロープで制御されることによって初めて可能になるのです。
どちらにせよ、前の例のそれぞれのコンポーネントに別々のエンベロープを与えることによって、より微妙なテクスチャーを生成することができます。
(
{ var n = 12;
Mix.arFill(
n, // n個のサイン波を生成する
{
SinOsc.ar( // それぞれがlow.rrand(high)の範囲内で起こりうる周波数
[67.0.rrand(2000), 67.0.rrand(2000)], // ... 実数の値
0,
n.reciprocal // それぞれのサイン波の振幅をスケーリングする
// nの値に従って
)
*
EnvGen.kr( // サイン波のそれぞれにエンベロープを与える
Env.sine(2.0.rrand(17)),
doneAction: 0 // サウンド全体が完了した時にだけ
// エンベロープを解放する(なぜ?)
)
}
)
* // パッチ全体に対してエンベロープを与える
EnvGen.kr(
Env.perc(11, 6),
doneAction: 2,
levelScale: 0.75
)
}.scope
)
(もしくはKlang ugenを用いることで同じ様な結果が得られます)
////////////////////////////////////////////////////////////////////////////////////////////////////
リング変調
2つのUGenを掛算することでリング変調を生成します。
// キャリアの振幅を低周波オシレータ(LFO)で変調
{ SinOsc.ar(440, 0, 0.571) * SinOsc.kr(2.reciprocal) }.scope
// LFOの周波数を可聴帯域にすることで追加の周波数成分を生成する
{ SinOsc.ar(440, 0, 0.571) * SinOsc.kr(880) }.scope
// 低周波のオシレータ(lfo)でモジュレータの振幅を変調する
(
{
SinOsc.ar(440, 0, 0.571)
*
(SinOsc.kr(880) // モジュレータとlfoを括弧の中に包み込む
* // なぜ?
SinOsc.kr([6.99, 8.01].reciprocal)
)
}.scope
)
////////////////////////////////////////////////////////////////////////////////////////////////////
振幅変調
2つのUGenを掛算する際にモジュレータの値を正の値に制限することで、Charles Dodgeが「クラシック」振幅変調と呼ぶものを生成します。
モジュレーター、もし低周波のオシレータである場合には、その出力が正の値に制限されているか正の値に制限されていないかの違いは、変調の振幅のみに表れます。UGenを正の値のみに制限するために.absメッセージを使っています。
// 低周波のオシレータ(lfo)でキャリアの振幅を変調する
{ SinOsc.ar(440, 0, 0.571) * SinOsc.kr(2.reciprocal).abs }.scope
// lfoを用いてモジュレータの振幅を変調する
(
{
SinOsc.ar(440, 0, 0.571)
*
(SinOsc.kr(880).abs // モジュレータとlfoを括弧の中に包み込む
* // なぜ?
SinOsc.kr([6.99, 8.01].reciprocal)
)
}.scope
)
////////////////////////////////////////////////////////////////////////////////////////////////////
モジュレータの出力を正の値に制限して低周波のオシレータの周波数を上げることにより、(サイドバンドを生成することによって)著しく音色が変化させます。
// lfoの周波数を可聴帯域にして.absメッセージをモジュレータに適用する
{ SinOsc.ar(440, 0, 0.571) * SinOsc.kr(880).abs }.scope
// 前の例をリング変調(.absメッセージの無いもの)と比較せよ
// ... 何が違うだろう?
{ SinOsc.ar(440, 0, 0.571) * SinOsc.kr(880) }.scope
////////////////////////////////////////////////////////////////////////////////////////////////////